// RedJey Graphics User Interface Library for J2ME Platform.
// Copyright (C) 2008 Yevgen Krapiva
//
// This library is free software; you can redistribute
// it and/or modify it under the terms of the
// GNU Lesser General Public License as published
// by the Free Software Foundation; either version 3.0
// of the License, or any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

package com.incrediblesoft.redjey;

/**
 * @author Yevgen Krapiva<br>
 *         Homepage: http://code.google.com/p/redjey/<br>
 *         Feedback: ykrapiva@gmail.com<br>
 *         Copyright (C) 2008 Yevgen Krapiva<br>
 */

import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;

public class CustomFont
{
    private static Hashtable fonts = new Hashtable(5);

	private String fontPath;

    private Image fontImage;
	private byte[] characterWidths;
	private short[] xPositions;
	private String characterMap;
	private int fontHeight;
    private boolean isCaseSensitive;

    private boolean isSystemFont = false;
    private Font font;
    private int face, style, size;
    private int color;

    private CustomFont(int face, int style, int size, int color)
    {
        this.face = face;
        this.style = style;
        this.size = size;
        this.color = color;
        font = Font.getFont(face, style, size);
        this.fontHeight = font.getHeight();
        this.isSystemFont = true;
    }

    private CustomFont(String fontPath) throws IOException
    {
        this.fontPath = fontPath;
        // try to load the *.fon file:
        InputStream is = null;
        DataInputStream dis = null;
        ByteArrayOutputStream baos = null;

        try
        {
            // try to load the *.fon file:
			is = getClass().getResourceAsStream(fontPath);
			if (is == null) return;

			dis = new DataInputStream( is );
			isCaseSensitive = dis.readBoolean();
            characterMap = dis.readUTF();
			int length = characterMap.length();
			characterWidths = new byte[ length ];
			xPositions = new short[ length ];
			short xPos = 0;
			for (int i = 0; i < length; i++ )
            {
				characterWidths[i] = dis.readByte();
				xPositions[i] = xPos;
                xPos += characterWidths[i];
			}

            fontImage = Image.createImage( is );
			fontHeight = this.fontImage.getHeight();
        }
        finally
        {
            try  {is.close();}
            catch (Exception ex) {}
            try  {dis.close();}
            catch (Exception ex) {}
            try  {baos.close();}
            catch (Exception ex) {}
        }
    }

    public static CustomFont getFont(String fontPath)
    {
        if (fonts.containsKey(fontPath))
            return (CustomFont) fonts.get(fontPath);
        else
        {
            CustomFont cf = null;
            try
            {
                cf = new CustomFont(fontPath);
                fonts.put(fontPath, cf);
            }
            catch (IOException ex)
            {
                ex.printStackTrace();
            }
            return cf;
        }
    }

    public static CustomFont getFont(int face, int style, int size, int color)
    {
        return new CustomFont(face, style, size, color);
    }

    public static CustomFont getFont(Object descObj)
    {
        if (descObj instanceof String)
        {
            return getFont((String) descObj);
        }
        else if (descObj instanceof Hashtable)
        {
            Hashtable styleDesc = (Hashtable) descObj;
            String faceStr = (String) styleDesc.get("face");
            String styleStr = (String) styleDesc.get("style");
            String sizeStr = (String) styleDesc.get("size");
            String colorStr = (String) styleDesc.get("color");

            int face = Font.FACE_SYSTEM;
            int style = 0;
            int size = Font.SIZE_SMALL;
            int color = 0;

            if (faceStr != null)
            {
                if (faceStr.equals("proportional"))
                    face = Font.FACE_PROPORTIONAL;
                else if (faceStr.equals("monospace"))
                    face = Font.FACE_MONOSPACE;
            }

            if (styleStr != null)
            {
                if (styleStr.indexOf("plain") != -1) style |= Font.STYLE_PLAIN;
                if (styleStr.indexOf("bold") != -1) style |= Font.STYLE_BOLD;
                if (styleStr.indexOf("italic") != -1) style |= Font.STYLE_ITALIC;
                if (styleStr.indexOf("underlined") != -1) style = Font.STYLE_UNDERLINED;
            }
            else
                style = Font.STYLE_PLAIN;

            if (sizeStr != null)
            {
                if (sizeStr.equals("small"))
                    size = Font.SIZE_SMALL;
                else if (sizeStr.equals("medium"))
                    size = Font.SIZE_MEDIUM;
                else if (sizeStr.equals("large"))
                    size = Font.SIZE_LARGE;
            }

            if (colorStr != null)
                color = (int) Long.parseLong(colorStr, 16);

            return getFont(face, style, size, color);
        }
        else
            return null;
    }

    public int getHeight()
    {
        return fontHeight;
    }

    public int charWidth(char ch)
    {
        if (isSystemFont)
            return font.charWidth(ch);
        else
        {
            int index = characterMap.indexOf(isCaseSensitive ? ch : Character.toLowerCase(ch));
            if (index == -1) characterMap.indexOf('?');
            if (index != -1)
                return this.characterWidths[index];
            return -1;
        }
    }

    public int stringWidth(String s)
    {
        if (isSystemFont)
            return font.stringWidth(s);
        else
        {
            int width = 0;
            int index;

            if (!isCaseSensitive)
                s = s.toLowerCase();

            for (int i=0; i<s.length(); i++)
            {
                index = characterMap.indexOf(s.charAt(i));
                if (index != -1) width += characterWidths[index];
            }
            return width;
        }
    }

    public int subStringWidth(String s, int offset, int length)
    {
        if (isSystemFont)
           return font.substringWidth(s, offset, length);
       else
       {
           int width = 0;
           int index;

           if (!isCaseSensitive)
               s = s.toLowerCase();

           for (int i=offset; i<length; i++)
           {
               index = characterMap.indexOf(s.charAt(i));
               if (index != -1) width += characterWidths[index];
           }

           return width;
       }
    }

    public int charsWidth(char[] chars, int offset, int length)
    {
        if (isSystemFont)
            return font.charsWidth(chars, offset, length);
        else
        {
            int width = 0;
            for (int i=offset; i<length; i++) width += charWidth(chars[i]);
            return width;
        }
    }

    public void drawString(Graphics g, String s, int x, int y, int alignment)
    {
        if (isSystemFont)
        {
            g.setColor(color);
            g.setFont(font);
            g.drawString(s, x, y, alignment);
        }
        else
        {
            char ch;

            if (alignment != (Graphics.LEFT | Graphics.TOP))
            {
                if ((alignment & Graphics.HCENTER) > 0)
                    x -= stringWidth(s) >> 1;
                else if ((alignment & Graphics.RIGHT) > 0)
                    x -= stringWidth(s);

                if ((alignment & Graphics.BASELINE) > 0)
                    y -= fontHeight >> 1;
                else if ((alignment & Graphics.BOTTOM) > 0)
                    y -= fontHeight;
            }

            for (int i=0; i<s.length(); i++)
            {
                ch = s.charAt(i);
                drawChar(g, ch, x, y, Graphics.LEFT | Graphics.TOP);
                x += charWidth(ch);
            }

        }
    }

    public void drawChar(Graphics g, char ch, int x, int y, int alignment)
    {
        if (isSystemFont)
        {
            g.setColor(color);
            g.setFont(font);
            g.drawChar(ch, x, y, alignment);
        }
        else
        {
            if (!isCaseSensitive)
                ch = Character.toLowerCase(ch);

            int index = characterMap.indexOf(ch);
            if (index == -1) index = characterMap.indexOf(ch);

            if (index < 0) return;
            

            int clipX = g.getClipX();
            int clipY = g.getClipY();
            int clipW = g.getClipWidth();
            int clipH = g.getClipHeight();

            int offset_x = xPositions[index];

            if (alignment != (Graphics.LEFT | Graphics.TOP))
            {
                if ((alignment & Graphics.HCENTER) > 0)
                    x -= characterWidths[index] >> 1;
                else if ((alignment & Graphics.RIGHT) > 0)
                    x -= characterWidths[index];

                if ((alignment & Graphics.BASELINE) > 0)
                    y -= fontHeight >> 1;
                else if ((alignment & Graphics.BOTTOM) > 0)
                    y -= fontHeight;
            }

            g.clipRect(x, y, characterWidths[index], fontHeight);

            g.drawImage(fontImage, x - offset_x, y, Graphics.LEFT | Graphics.TOP);

            g.setClip(clipX, clipY, clipW, clipH);
        }
    }

    public void drawChars(Graphics g, char[] data, int offset, int length, int x, int y, int anchor)
    {
        if (isSystemFont)
        {
            g.setColor(color);
            g.setFont(font);
            g.drawChars(data, offset, length, x, y, anchor);
        }
        else
        {
        }
    }

    /**
     * Returns font's color. Has no sense for custom fonts.
     * @return
     */
    public int getColor()
    {
        return color;
    }
    /**
     * Sets font color. Has no effect for custom fonts
     * @param color
     */
    public void setColor(int color)
    {
        this.color = color;
    }

    public CustomFont clone()
    {
        if (this.isSystemFont)
            return getFont(face, style, size, color);
        else
            return getFont(fontPath);
    }

    /**
     * Breaks continuous text into set of small strings, each of them is not greater than the given limit.
     * @param text Text to break
     * @param font Font to use
     * @param windowWidth maximum string width.
     * @param numCols maximum string length in characters.
     * @return Formatted text
     *
     * The text is broken into set of lines depending on the given limits.
     * If you want to set a limit in pixels, use windowWidth, otherwise set this parameter to 0.
     * If you want to set a limit in characters, use numCols, otherwise set this parameter to 0.
     * If windowWidth and numCols are zero the text is broken by matching only new-line character. 
     */
    public static String[] splitString(String text, CustomFont font, int windowWidth, int numCols)
    {
        Vector out = new Vector();
        int start = 0, end = 0, lastSpace = -1;
        int curWidth = 0;
        int curCols = 0;
        char ch;
        int length = text.length();

        for (int pos = 0; pos < length; pos++)
        {
            ch = text.charAt(pos);
            if (ch == '\n')
            {
                out.addElement(text.substring(start, pos));
                curWidth = 0;
                curCols = 0;
                lastSpace = start = pos + 1;
                continue;
            }
            if (ch == ' ' && lastSpace != pos - 1)
            {
                lastSpace = pos;
                if (start == pos)
                {
                    start++;
                    continue;
                }
            }

            if (windowWidth > 0)
                curWidth += font.charWidth(ch);
            else if (numCols > 0)
                curCols++;

            if ((windowWidth > 0 && curWidth > windowWidth) || (numCols > 0 && curCols > numCols))
            {
                end = (lastSpace > start) ? lastSpace + 1 : pos;
                out.addElement(text.substring(start, end));
                curWidth = 0;
                curCols = 0;
                lastSpace = start = end;
                pos = end - 1;
            }
        }


        if (start != length)
            out.addElement(text.substring(start, length));

        String[] outStrings = new String[out.size()];
        out.copyInto(outStrings);

        return outStrings;
    }

}
